package com.yc.spring;

import com.yc.cinema.BeanConfig;
import com.yc.cinema.biz.UserBiz;
import com.yc.cinema.web.IndexAction;
import com.yc.cinema.web.UserAction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

public class MyApplicationContext {

    public static void main(String[] args) {
        MyApplicationContext cxt = new MyApplicationContext(BeanConfig.class);
        System.out.println((IndexAction)cxt.getBean("iAction"));
        System.out.println(cxt.getBean(UserBiz.class));
        // 测试 @Resource
        UserAction userAction = cxt.getBean("userAction");
        System.out.println(userAction.getUbiz());
        System.out.println(userAction.getCbiz());
        // 测试 @Autowired
        IndexAction indexAction = cxt.getBean("iAction");
        System.out.println(indexAction.getMbiz());
        System.out.println(indexAction.getUbiz());

    }

    // 配置类
    private final Class<?> configClass;
    // 存放扫描出来的组件类
    private Set<Class<?>> components = new HashSet<>();
    private Map<String,Object> beans = new HashMap<>();

    // 容器初始化
    public MyApplicationContext(Class<?> cls) {
        // 判断传入的类对象是不是配置类
        if (cls.getAnnotation(Configuration.class) == null) {
            throw new RuntimeException(cls + "不是IOC容器配置类");
        }
        this.configClass = cls;
        // 扫包
        scanPackage();
        // 创建所有的bean
        createBean();
        // 自动装配
        autowire();
    }

    private void autowire() {
        // 对所有的bean进行迭代
        beans.forEach((name,bean)->{
            // 对每个bean的属性迭代
            Object fieldBean;
            for (Field field : bean.getClass().getDeclaredFields()) {
                // 判断每个属性是否需要注入
                if(field.getAnnotation(Resource.class)!=null){
                    // @Resource byName=>byType
                    Resource resource = field.getAnnotation(Resource.class);
                    String id = resource.name();
                    if(id.isEmpty()){
                        // Resource 没有设置 name, name使用属性名
                        id = field.getName();
                    }
                    fieldBean = getBean(id);
                    // byName 失败
                    if(fieldBean==null){
                        // byType  field.getClass() ==> Field 错误
                        // fieldBean = getBean(field.getClass());
                        fieldBean = getBean(field.getType());
                    }
                } else if (field.getAnnotation(Autowired.class)!=null){
                    // @Autowired byType + byName
                    if(field.getAnnotation(Qualifier.class)!=null){
                        String id = field.getAnnotation(Qualifier.class).value();
                        fieldBean = getBean(id);
                    } else {
                        fieldBean = getBean(field.getType());
                    }
                } else {
                    // 该属性不要注入
                    continue;
                }
                // 将对象设置到 属性中
                field.setAccessible(true);
                final Object _fieldBean = fieldBean;
                exec(()->{
                    field.set(bean, _fieldBean);
                    return null;
                });
            }
        });
        // 返回bean
    }

    private void createBean() {
        components.forEach(c->{
            // 创建对象
            Object bean = exec(()->c.newInstance());
            // 定名字
            String name = null;
            if(c.getAnnotation(Controller.class)!=null) {
                name = c.getAnnotation(Controller.class).value();
            } else if(c.getAnnotation(Service.class)!=null) {
                name = c.getAnnotation(Service.class).value();
            } else if(c.getAnnotation(Repository.class)!=null) {
                name = c.getAnnotation(Repository.class).value();
            } else if(c.getAnnotation(Component.class)!=null) {
                name = c.getAnnotation(Component.class).value();
            }
            if(name.isEmpty()){
                // 默认的组件名是 类名+首字母小写
                name = c.getSimpleName().substring(0,1).toLowerCase()
                        + c.getSimpleName().substring(1);
            }
            beans.put(name, bean);
        });
        beans.forEach((key,bean)->{
            System.out.printf(" %s %s \n", key, bean);
        });
    }

    /**
     * 扫包 => 扫出所有标注为组件的类
     */
    private void scanPackage() {
        ComponentScan scan = configClass.getAnnotation(ComponentScan.class);
        if (scan == null) {
            throw new RuntimeException("全剧终...");
        }
        String[] value = scan.value();
        if (value.length == 0) {
            throw new RuntimeException("全剧终...");
        }
        for (String p : value) {
            // com.yc.package ==> com/yc/package
            p = p.replaceAll("\\.", "/");
            URL url = getClass().getClassLoader().getResource(p);
            String baseDir = url.getPath().substring(1);
            String dir = exec(()->URLDecoder.decode(baseDir, "utf-8"));
            scanPackage(new File(dir));
        }
    }

    private void scanPackage(File dir) {
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                // 是目录, 则递归调用, 继续扫
                scanPackage(file);
            } else if (file.isFile()) {
                // 如果是文件, 则判断是否是组件
                // 从文件路径中提取类路径
                String path = file.getAbsolutePath();
                int index = path.indexOf("classes\\");
                path = path.substring(index + "classes\\".length());
                path = path.replace(".class", "");
                path = path.replaceAll("\\\\", ".");
                String classpath = path;
                Class<?> cls = exec(()->Class.forName(classpath));
                // 判断组件类
                if (cls.getAnnotation(Controller.class) != null
                        || cls.getAnnotation(Service.class) != null
                        || cls.getAnnotation(Repository.class) != null
                        || cls.getAnnotation(Component.class) != null) {
                    // 添加组件类
                    components.add(cls);
                }
            }
        }
    }

    public <T> T getBean(Class<T> cls) {
        Set<Object> beans = new HashSet<>();
        // 查找所有满足条件的bean, 当前类及其子类
        this.beans.values().forEach(bean->{
            // 判断当前类是否是 cls 的子类
            if(bean.getClass().isAssignableFrom(cls)){
                beans.add(bean);
            }
        });
        if(beans.size() == 0){
            throw new RuntimeException("没有该类的bean:" + cls);
        } else if(beans.size() == 1){
            return (T) beans.iterator().next();
        } else {
            // 扫描到了多个 符合条件的 bean, 找 标记为 @Primary 的bean
            for (Object bean : beans) {
                if(bean.getClass().getAnnotation(Primary.class)!=null){
                    return (T) bean;
                }
            }
            throw new RuntimeException("查找到多个同类的bean: " + cls);
        }
    }

    public <T> T getBean(String name) {
        return (T) beans.get(name);
    }

    // 简化 异常处理
    public <T> T exec(Function<T> function) {
        try {
            return function.exec();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 函数接口
    interface Function<T> {
        T exec() throws Exception;
    }

}
